/*
 *	cfgio::Input class implementation
 *
 *	This file is part of OTAWA
 *	Copyright (c) 2014, IRIT UPS.
 *
 *	OTAWA is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	OTAWA is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with OTAWA; if not, write to the Free Software
 *	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <elm/avl/Map.h>
#include <elm/io/BlockInStream.h>
#include <elm/data/HashMap.h>
#include <elm/util/UniquePtr.h>
#include <elm/util/Pair.h>
#include <elm/xom/dtd.h>

#include <otawa/cfg/features.h>
#include <otawa/cfgio/features.h>
#include <otawa/cfgio/Input.h>
#include <otawa/proc/Processor.h>
#include <otawa/prog/Process.h>
#include <otawa/prog/TextDecoder.h>
#include <otawa/prog/WorkSpace.h>

namespace otawa { namespace cfgio {

using namespace elm;

// CFG file DTD
static dtd::Element entry("entry", dtd::EMPTY);
static dtd::IDAttribute entry_id(entry, "id", dtd::REQUIRED);
static dtd::Element exit("exit", dtd::EMPTY);
static dtd::IDAttribute exit_id(exit, "id", dtd::REQUIRED);

static dtd::Element bb("bb", dtd::ignored);
static dtd::IDAttribute bb_id(bb, "id", dtd::REQUIRED);
static dtd::Attribute<t::uint32> address(bb, "address", 0, dtd::STRICT);
static dtd::Attribute<t::uint32> size(bb, "size", 0, dtd::STRICT);
static dtd::RefAttribute<CFGMaker> call(bb, "call", dtd::STRICT | dtd::FORWARD);

static dtd::Element edge("edge", dtd::ignored);
static dtd::RefAttribute<BasicBlock *> source(edge, "source", dtd::STRICT | dtd::REQUIRED);
static dtd::RefAttribute<BasicBlock *> target(edge, "target", dtd::STRICT | dtd::REQUIRED);

static dtd::Element property("property", dtd::ignored);

static dtd::Element cfg("cfg", (*property, entry, *bb, exit, *edge));
static dtd::IDAttribute cfg_id(cfg, "id", dtd::STRICT | dtd::REQUIRED);
static dtd::Attribute<t::uint32> cfg_address(cfg, "address", dtd::STRICT | dtd::REQUIRED);

static dtd::Element cfg_collection("cfg-collection", (*property, cfg, *cfg));

// CFG factory
class CFGFactory: public dtd::Factory {
public:

	CFGFactory(WorkSpace *workspace)
		: ws(workspace), g(nullptr), v(nullptr) { }

	~CFGFactory() {
		for(auto g: gs)
			delete g;
	}

	void begin(elm::dtd::Element &element) override {
		if(element == cfg) {
			g = new CFGMaker(ws->findInstAt(*cfg_address));
			gs.add(g);
		}
		else if(element == bb) {
			if(call.isSet()) {
				auto c = new otawa::SynthBlock();
				if(call.done())
					g->call(c, **call);
				else
					g->call(c, nullptr);
				v = c;
			}
			else {
				Vector<Inst *> is;
				Address ea = Address(*address + *size);
				for(auto i = ws->findInstAt(*address);
				i != nullptr && i->address() < ea;
				i = i->nextInst())
					is.add(i);
				v = new BasicBlock(is.detach());
				g->add(v);
			}
		}
		else if(element == entry)
			v = g->entry();
		else if(element == exit)
			v = g->exit();
	}

	void end(elm::dtd::Element &element) override {
		if(element == cfg)
			g = nullptr;
		else if(element == bb || element == entry || element == exit)
			v = nullptr;
	}

	void* getRef(elm::dtd::Element &element) override {
		if(element == cfg)
			return g;
		else if(element == bb || element == entry || element == exit)
			return v;
		else
			return nullptr;
	}

	void* getPatchRef(elm::dtd::AbstractAttribute &attr) override {
		if(attr == call)
			return new Pair<CFGMaker *, Block *>(g, v);
		else
			return nullptr;
	}

	void patch(elm::dtd::AbstractAttribute &attr, void *object, void *ref) override {
		if(attr == call) {
			auto p = static_cast<Pair<CFGMaker *, Block *> *>(object);
			auto c = static_cast<SynthBlock *>(p->snd);
			p->fst->fix(c, static_cast<CFGMaker *>(ref));
			delete p;
		}
	}

	WorkSpace *ws;
	CFGMaker *g;
	Block *v;
	Vector<CFGMaker *> gs;
};


/**
 * @addtogroup cfgio
 *
 * @section cfgio-input Input
 * The cfgio::Input provides the processor to construct CFGCollection (which is stored to INVOLVED_CFG(props)) from an XML file.
 * The XML file can be generated by using the cfgio::Output processor, or written by hand.
 * Users can use the manually created XML file to create CFGs of the desired topologies.
 * To create BasicBlock which does not exist in the program, one has to include the instruction with the address at 0xCAFEBABE to indicate
 * that such BasicBlock is created from scratch.
 * @code
 * 	<?xml version="1.0" encoding="UTF-8"?>
 * 	<cfg-collection>
 * 		<cfg id="test1">
 * 			<entry id="0"/>
 * 			<bb id="1"><inst address="0xCAFEBABE"/></bb>
 * 			<bb id="2"><inst address="0xCAFEBABE"/></bb>
 * 			<bb id="3"><inst address="0xCAFEBABE"/></bb>
 * 			<exit id="4" />
 * 			<edge source="0" target="1"/>
 * 			<edge source="1" target="2"/>
 * 			<edge source="1" target="3"/>
 * 			<edge source="2" target="3"/>
 * 			<edge source="3" target="4"/>
 * 		</cfg>
 * 	</cfg-collection>
 * @endcode
 *
 * otawa::cfgio::FROM can be used to specified the PATH of the XML file to be read (the defualt is main.xml).
 * @code
 * your_program --add-prop otawa::cfgio::FROM=PATH_TO_YOUR_XML_FILE
 * @endcode
 *
 * The feature of cfgio::Input can be used as with the feature otawa::cfgio::CFG_FILE_INPUT_FEATURE.
 * @code
 * workspace()->require(DynFeature("otawa::cfgio::CFG_FILE_INPUT_FEATURE"), props);
 * @endcode
 */

/**
 * @class Input
 * Create CFGCollection from an XML file matching the DTD ${OTAWA_HOME}/share/Otawa/dtd/cfg.dtd .
 * @ingroup cfgio
 */
Input::Input(p::declare& r): Processor(r), coll(nullptr) {
}


/**
 *
 */
void Input::configure(const PropList& props) {
	Processor::configure(props);
	path = FROM(props);
	if(!path) {
		string task = TASK_ENTRY(props);
		if(task)
			path = task + ".xml";
	}
}


/**
 *
 */
void Input::processWorkSpace(WorkSpace *ws) {
	CFGFactory factory(ws);

	// open the document
	xom::Builder builder;
	xom::Document *doc = builder.build(path.toString().toCString());
	if(!doc)
		throw ProcessorException(*this, _ << " cannot open " << path);

	// get the top element
	xom::Element *top = doc->getRootElement();
//#	if 0
	dtd::Parser parser(factory, cfg_collection);
	try {
		parser.parse(top);
		coll = new CFGCollection();
		for(auto m: factory.gs) {
			auto g = m->build();
			coll->add(g);
		}
	}
	catch(dtd::Exception& e) {
		throw ProcessorException(*this, _ << "format error in " << path << ": " << e.message());
	}
//#	endif
}

/**
 *
 */
void Input::cleanup(WorkSpace *ws) {
	ENTRY_CFG(ws) = coll->get(0);
	INVOLVED_CFGS(ws) = coll;
}

///
void Input::Input::destroy(WorkSpace *ws) {
	ENTRY_CFG(ws).remove();
	INVOLVED_CFGS(ws).remove();
	delete coll;
	coll = nullptr;
}


///
void *Input::interfaceFor(const otawa::AbstractFeature &feature) {
	if(feature == otawa::COLLECTED_CFG_FEATURE)
		return coll;
	else
		return nullptr;
}


/**
 * Feature causing the load of a CFG stored to a file. This feature induces
 * also the implementation of otawa::COLLECTED_CFG_FEATURE.
 *
 * Configuration properties encompass:
 * * otawa::cfgio::FROM to select the path to load the CFG from.
 *
 * @ingroup cfgio
 */
p::feature CFG_FILE_INPUT_FEATURE("otawa::cfgio::CFG_FILE_INPUT_FEATURE", new Maker<Input>());


///
p::declare Input::reg = p::init("otawa::cfgio::Input", Version(1, 0, 0))
	.maker<Input>()
	.require(otawa::DECODED_TEXT)
	.provide(otawa::COLLECTED_CFG_FEATURE)
	.provide(otawa::cfgio::CFG_FILE_INPUT_FEATURE);

/**
 * Defines the path to the file to read the CFG from.
 * @ingroup cfgio
 */
Identifier<Path> FROM("otawa::cfgio::FROM");

} }	// otawa::cfgio
